using FluffyUnderware.Curvy.Generator;
using FluffyUnderware.Curvy.Utils;
using FluffyUnderware.DevTools;
using System;
using UnityEngine;

namespace FluffyUnderware.Curvy.Controllers
{
	[AddComponentMenu("Curvy/Controller/CG Volume Controller", 8)]
	[HelpURL("https://curvyeditor.com/doclink/volumecontroller")]
	public class VolumeController : CurvyController
	{
		private const float CrossPositionRangeMin = -0.5f;

		private const float CrossPositionRangeMax = 0.5f;

		[Section("General", true, false, 100)]
		[CGDataReferenceSelector(typeof(CGVolume), Label = "Volume/Slot")]
		[SerializeField]
		private CGDataReference m_Volume = new CGDataReference();

		[Section("Cross Position", true, false, 100, Sort = 1, HelpURL = "https://curvyeditor.com/doclink/volumecontroller_crossposition")]
		[SerializeField]
		[FloatRegion(UseSlider = true, Precision = 4, RegionOptionsPropertyName = "CrossRangeOptions", Options = AttributeOptionsFlags.Full)]
		private FloatRegion m_CrossRange = new FloatRegion(-0.5f, 0.5f);

		[RangeEx("MinCrossRelativePosition", "MaxCrossRelativePosition", "", "")]
		[SerializeField]
		private float crossRelativePosition;

		[SerializeField]
		private CurvyClamping m_CrossClamping;

		[SerializeField]
		[HideInInspector]
		[Obsolete("Use crossRelativePosition instead. This field is kept for retro compatibility reasons")]
		private float m_CrossInitialPosition;

		public CGDataReference Volume
		{
			get
			{
				return m_Volume;
			}
			set
			{
				m_Volume = value;
			}
		}

		public CGVolume VolumeData => (!Volume.HasValue) ? null : Volume.GetData<CGVolume>();

		public float CrossFrom
		{
			get
			{
				return m_CrossRange.From;
			}
			set
			{
				m_CrossRange.From = Mathf.Clamp(value, -0.5f, 0.5f);
			}
		}

		public float CrossTo
		{
			get
			{
				return m_CrossRange.To;
			}
			set
			{
				m_CrossRange.To = Mathf.Clamp(value, CrossFrom, 0.5f);
			}
		}

		public float CrossLength => m_CrossRange.Length;

		public CurvyClamping CrossClamping
		{
			get
			{
				return m_CrossClamping;
			}
			set
			{
				m_CrossClamping = value;
			}
		}

		public float CrossRelativePosition
		{
			get
			{
				return GetClampedCrossPosition(crossRelativePosition);
			}
			set
			{
				crossRelativePosition = GetClampedCrossPosition(value);
			}
		}

		public override float Length => (VolumeData == null) ? 0f : VolumeData.Length;

		public override bool IsReady => Volume != null && !Volume.IsEmpty && Volume.HasValue;

		private RegionOptions<float> CrossRangeOptions => RegionOptions<float>.MinMax(-0.5f, 0.5f);

		private float MinCrossRelativePosition => m_CrossRange.From;

		private float MaxCrossRelativePosition => m_CrossRange.To;

		public float CrossRelativeToAbsolute(float relativeDistance)
		{
			return (VolumeData == null) ? 0f : VolumeData.CrossFToDistance(base.RelativePosition, relativeDistance, CrossClamping);
		}

		public float CrossAbsoluteToRelative(float worldUnitDistance)
		{
			return (VolumeData == null) ? 0f : VolumeData.CrossDistanceToF(base.RelativePosition, worldUnitDistance, CrossClamping);
		}

		protected override float RelativeToAbsolute(float relativeDistance)
		{
			return (VolumeData == null) ? 0f : VolumeData.FToDistance(relativeDistance);
		}

		protected override float AbsoluteToRelative(float worldUnitDistance)
		{
			return (VolumeData == null) ? 0f : VolumeData.DistanceToF(worldUnitDistance);
		}

		protected override Vector3 GetInterpolatedSourcePosition(float tf)
		{
			return Volume.Module.Generator.transform.TransformPoint(VolumeData.InterpolateVolumePosition(tf, CrossRelativePosition));
		}

		protected override void GetInterpolatedSourcePosition(float tf, out Vector3 interpolatedPosition, out Vector3 tangent, out Vector3 up)
		{
			VolumeData.InterpolateVolume(tf, CrossRelativePosition, out interpolatedPosition, out tangent, out up);
			Transform transform = Volume.Module.Generator.transform;
			interpolatedPosition = transform.TransformPoint(interpolatedPosition);
			tangent = transform.TransformDirection(tangent);
			up = transform.TransformDirection(up);
		}

		protected override Vector3 GetTangent(float tf)
		{
			return Volume.Module.Generator.transform.TransformDirection(VolumeData.InterpolateVolumeDirection(tf, CrossRelativePosition));
		}

		protected override Vector3 GetOrientation(float tf)
		{
			return Volume.Module.Generator.transform.TransformDirection(VolumeData.InterpolateVolumeUp(tf, CrossRelativePosition));
		}

		protected override void Advance(float speed, float deltaTime)
		{
			float tf = base.RelativePosition;
			MovementDirection curyDirection = base.MovementDirection;
			SimulateAdvance(ref tf, ref curyDirection, speed, deltaTime);
			base.MovementDirection = curyDirection;
			base.RelativePosition = tf;
		}

		protected override void SimulateAdvance(ref float tf, ref MovementDirection curyDirection, float speed, float deltaTime)
		{
			int direction = curyDirection.ToInt();
			switch (base.MoveMode)
			{
			case MoveModeEnum.Relative:
				VolumeData.Move(ref tf, ref direction, speed * deltaTime, base.Clamping);
				break;
			case MoveModeEnum.AbsolutePrecise:
				VolumeData.MoveBy(ref tf, ref direction, speed * deltaTime, base.Clamping);
				break;
			default:
				throw new NotSupportedException();
			}
			curyDirection = MovementDirectionMethods.FromInt(direction);
		}

		private float GetClampedCrossPosition(float position)
		{
			return CurvyUtility.ClampValue(position, CrossClamping, CrossFrom, CrossTo);
		}

		public override void OnAfterDeserialize()
		{
			base.OnAfterDeserialize();
			if (!float.IsNaN(m_CrossInitialPosition))
			{
				crossRelativePosition = DTMath.MapValue(CrossFrom, CrossTo, m_CrossInitialPosition, -0.5f, 0.5f);
				m_CrossInitialPosition = float.NaN;
			}
		}
	}
}
